/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package shaman.storage.impl.file;

import shaman.storage.impl.util.FilterIterator;
import com.thoughtworks.xstream.XStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import shaman.storage.impl.util.ConverterIterable;
import shaman.storage.impl.util.ObjectStreamIterator;

/**
 *
 * @author dmitriy
 */
public class FilePropertiesCache implements Iterable<StoredFileProperties> {

    public static String FILES_ELEMENT="files";
    private FileContentsAccessor acc;
    private OutputStream ostr;
    private ObjectOutputStream out;

    private String lastFileName=null;
    private StoredFileProperties lastFile=null;

    private Map<String, SoftReference<StoredFileProperties>> cache=new HashMap<String, SoftReference<StoredFileProperties>>();

    private static class FilePropertiesIterator extends FilterIterator<Map> {

        private ObjectInputStream oi;
        private InputStream is;

        protected FilePropertiesIterator(Iterator<Map> baseIter, ObjectInputStream oi, InputStream is) {
            super(baseIter);
            this.oi=oi;
            this.is=is;
        }

        @Override
        public Map next() {
            Map ret=baseIter.next();
            return ret;
        }

        @Override
        protected void iterationEnded() {
            try {
                try {
                    oi.close();
                } finally {
                    is.close();
                }
            } catch (IOException ex) {
            }
        }

    }

    private class StoredFilesIterator extends ConverterIterable<Map, StoredFileProperties> {

        public StoredFilesIterator(Iterable<? extends Map> base) {
            super(base);
        }

        @Override
        protected StoredFileProperties convert(Map baseValue) {
            StoredFileProperties ret=new StoredFileProperties(null, (Map<String, String>) baseValue);
            ret.setImmutable();
            put(ret);
            return ret;
        }

    }

    private class PropertiesStorageIterable implements Iterable<Map> {

        public PropertiesStorageIterable() {
        }

        public Iterator<Map> iterator() {
            if (out!=null)
                throw new IllegalStateException("Cannot iterate over an incomplete set");
            try {
                InputStream is=acc.fetchFileContents();
                ObjectInputStream oi;
                try {
                    XStream xs=new XStream();
                    xs.processAnnotations(new Class[]{
                       StoredFileProperties.class
                    });
                    oi=xs.createObjectInputStream(is);
                    return new FilePropertiesIterator(new ObjectStreamIterator<Map>(oi, Map.class), oi, is);
                } catch (Throwable t) {
                    is.close();
                    throw t;
                }
            } catch (Throwable t) {
                throw new IterationException("Unable to create iterator", t);
            }
        }

    }
    
    public FilePropertiesCache(FileContentsAccessor acc) {
        this.acc=acc;
        ostr=null;
        out=null;
    }

    public FilePropertiesCache(FileContentsAccessor acc, boolean out) throws IOException {
        this.acc=acc;
        ostr=acc.storeFileContents();
        XStream xs=new XStream();
        xs.alias("properties", Map.class, HashMap.class);
        this.out=xs.createObjectOutputStream(ostr, FileBackup.COMPONENT_FILES);
    }

    protected synchronized void put(StoredFileProperties file) {
        String fileName=file.getFileName();
        lastFileName=fileName;
        lastFile=file;
        cache.put(fileName, new SoftReference<StoredFileProperties>(file));
    }

    protected synchronized StoredFileProperties tryFetch(String fileName) {
        if (fileName.equals(lastFileName))
            return lastFile;
        SoftReference<StoredFileProperties> ref=cache.get(fileName);
        StoredFileProperties ret=null;
        if (ref!=null)
            ret=cache.get(fileName).get();
        if (ret==null) {
            for (Iterator<Map.Entry<String, SoftReference<StoredFileProperties>>> i=cache.entrySet().iterator(); i.hasNext(); )
            {
                StoredFileProperties file=i.next().getValue().get();
                if (file==null)
                    i.remove();
            }
            return null;
        }
        lastFileName=fileName;
        lastFile=ret;
        return ret;
    }

    protected synchronized StoredFileProperties fetchDirect(String filename) {
        for (StoredFileProperties file: this) {
            if (filename.equals(file.getFileName()))
            {
                put(file);
                return file;
            }
        }
        return null;
    }

    public StoredFileProperties fetch(String filename) throws IOException, FileNotFoundException {
        try {
            StoredFileProperties file=tryFetch(filename);
            if (file==null)
                file=fetchDirect(filename);
            if (file==null)
                throw new FileNotFoundException("No such file");
            return new StoredFileProperties(file);
        } catch (IterationException ex) {
            Throwable exx=ex.getCause();
            if (exx instanceof IOException)
                throw (IOException) exx;
            else
                throw ex;
        }
    }

    protected void writeProperties(StoredFileProperties file) throws IOException {
        if (out==null)
            throw new IllegalStateException("Cannot add files to a finished set");
        file.setImmutable();
        out.writeObject(file.getProperties());
    }

    public Iterator<StoredFileProperties> iterator() {
        return new StoredFilesIterator(new PropertiesStorageIterable()).iterator();
    }

    public StoredFileProperties createFile(String fileName, Map<String, String> properties) {
        StoredFileProperties stor=new StoredFileProperties(fileName, properties);
        return stor;
    }

    public synchronized void stopWriting() throws IOException {
        for (SoftReference<StoredFileProperties> fref: cache.values()) {
            StoredFileProperties file=fref.get();
            if (file==null)
                continue;
            if (!file.isImmutable())
                throw new IOException("Mutable objects still in cache");
        }
        out.close();
        ostr.close();
        out=null;
        ostr=null;
    }

}
